package com.leyou.search.service.impl;

import com.baomidou.mybatisplus.extension.api.R;
import com.leyou.common.exception.LyException;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.BrandDTO;
import com.leyou.item.dto.CategoryDTO;

import com.leyou.search.dto.SearchRequest;
import com.leyou.search.entity.Goods;
import com.leyou.search.repostitory.GoodsRepository;
import com.leyou.search.service.SearchService;
import com.leyou.starter.elastic.entity.PageInfo;
import com.sun.org.apache.bcel.internal.generic.NEW;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;

import org.apache.ibatis.annotations.Param;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.Nested;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private ItemClient itemClient;

    @Autowired
    private GoodsRepository goodsRepository;


    @Override
    public Mono<List<String>> generateSuggest(String key) {

        if (StringUtils.isEmpty(key)) {
            throw new LyException(400, "参数不能为空");
        }

        //
        Mono<List<String>> mono = this.goodsRepository.suggestBySingleField("suggestion", key);//自动补全的功能


        return mono;
    }

    //todo 页面数据的分页查询
    @Override
    public Mono<PageInfo<Goods>> listGoods(SearchRequest searchRequest) {

        SearchSourceBuilder builder = new SearchSourceBuilder();//搜索条件

        QueryBuilder queryBuilder = getQueryBuilder(searchRequest);

        //开始封装查询条件
        //添加查询条件,查询属性为 title ,分词查询, 条件为and 并且
        builder.query(queryBuilder);
        //分页查询条件

        Integer page = searchRequest.getPage();
        int from = page == null ? 0 : (page - 1) * searchRequest.getSIZE();//如果page==null设置为0,不为空为后面的
        builder.from(from);//分页 每页从多少条开始
        builder.size(searchRequest.getSIZE());//分页 每页显示多少条

        Set<String> sortList = new HashSet<>();//有关排序字段的集合
        sortList.add("prices");
        sortList.add("sold");
        sortList.add("uodateTime");
        //排序条件             不为空                                   并且 排序字段在排序集合里面
        if (StringUtils.isNotEmpty(searchRequest.getSortBy()) && sortList.contains(searchRequest.getSortBy())) {//不为空 为ture
            //排序字段存在,采取排序
            builder.sort(searchRequest.getSortBy(), searchRequest.getDesc() ? SortOrder.DESC : SortOrder.ASC);
            //如果getSortBy() 为true dese 否则为 asc
        }


        //高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();//高亮条件对象
        highlightBuilder.preTags("<am>");//设置高亮标签
        highlightBuilder.postTags("</am>");//设置高亮标签
        highlightBuilder.field("title");//设置高亮字段(属性)


        builder.highlighter(highlightBuilder);


        Mono<PageInfo<Goods>> pageInfoMono = goodsRepository.queryBySourceBuilderForPageHighlight(builder);
        return pageInfoMono;
    }

    //构造查询条件
    private QueryBuilder getQueryBuilder(SearchRequest searchRequest) {
        String key = searchRequest.getKey();//搜索的条件 关键字;
        if (StringUtils.isEmpty(key)) {
            throw new LyException(400, "查询参数不能为空");
        }

        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

        //给bool查询添加查询条件
        boolQueryBuilder.must(QueryBuilders.matchQuery("title", key).operator(Operator.AND));
        //过滤条件是否添加 ,取决于FilterMap中是否有值
        if (!CollectionUtils.isEmpty(searchRequest.getFilters())){//不为空 出来
            for (Map.Entry<String, String> filterEntry : searchRequest.getFilters().entrySet()) {
                //过滤条件的key值 例如 CPU品牌 内存  机身存储 等等
                String queryKey = filterEntry.getKey();
                //过滤条件的 value值 例如 CPU品牌对应的 联发科 内存对应的 3GB等等
                String queryValue = filterEntry.getValue();

                //key替换为 brandId或者categoryId
                if ("品牌".equals(queryKey)){
                    boolQueryBuilder.filter(QueryBuilders.termQuery("brandId",queryValue));
                }else if("分类".equals(queryKey)){
                    boolQueryBuilder.filter(QueryBuilders.termQuery("categoryId",queryValue));
                }else {  //其他参数, 查询本身nested 查询
                    BoolQueryBuilder subBoolean = QueryBuilders.boolQuery();
                    subBoolean.must(QueryBuilders.termQuery("specs.name",queryKey));
                    subBoolean.must(QueryBuilders.termQuery("specs.value",queryValue));
                    boolQueryBuilder.filter(QueryBuilders.nestedQuery("specs",subBoolean, ScoreMode.None));
                }
            }
        }


        return boolQueryBuilder;
    }


    //todo 页面搜索过滤条件的获取
    @Override
    public Mono<Map<String, List<?>>> listFilter(SearchRequest searchRequest) {

        //五大条件对象
        SearchSourceBuilder builder = new SearchSourceBuilder();
        //添加查询条件
        builder.query(getQueryBuilder(searchRequest));
        //这是一个聚合, 要查询,但是不展示结果,提高效率
        builder.size(0);
        //添加两个聚合条件
        builder.aggregation(AggregationBuilders.terms("brandAgg").field("brandId").size(30));
        builder.aggregation(AggregationBuilders.terms("categoryAgg").field("categoryId").size(30));

        //添加规格参数的 nested集合 以及对应的子聚合
        builder.aggregation(
                AggregationBuilders.nested("specsAgg", "specs")
                        .subAggregation(AggregationBuilders.terms("nameAgg").field("specs.name").size(30)
                                .subAggregation(AggregationBuilders.terms("valueAgg").field("specs.value").size(30)))
        );


        //异步聚合处理
        Mono<Aggregations> aggregationsMono = this.goodsRepository.aggregationBySourceBuilder(builder);//返回的是map 键值对


        //mono中的map方法,对每个元素进行挨个运算, mono其实就1个, 对mono中的内容进行二次封装
        return aggregationsMono.map(//可以排序
//解析聚合
//ParsedLongTerms  根据聚合名称 获得聚合结果对象 buckets[]
//不为空去查
//不为空去查
                /*aggregations -> {
                    Map<String, List<?>> filterResult = new LinkedHashMap<>();//可以排序
                    //解析聚合
                    //ParsedLongTerms  根据聚合名称 获得聚合结果对象 buckets[]
                    Terms brandAgg = aggregations.get("brandAgg");
                    List<Long> brandIds = brandAgg
                            .getBuckets()
                            .stream()
                            .map(bucket -> ((Terms.Bucket) bucket).getKeyAsNumber())
                            .map(Number::longValue)
                            .collect(Collectors.toList());
                    if (!CollectionUtils.isEmpty(brandIds)) {//不为空去查
                        List<BrandDTO> brandDTOS = this.itemClient.listBrandByBid(brandIds);
                        filterResult.put("品牌", brandDTOS);
                    }
                    //根据聚合名称 获取聚合结果
                    Terms categoryAgg = aggregations.get("categoryAgg");
                    //从聚合结果中获取当前 聚合结果key 的值
                    List<Long> categoryIds = categoryAgg.getBuckets()
                            .stream()
                            .map(bucket -> ((Terms.Bucket) bucket)
                                    .getKeyAsNumber())
                            .map(Number::longValue)
                            .collect(Collectors.toList());
                    if (!CollectionUtils.isEmpty(categoryIds)) {//不为空去查
                        List<CategoryDTO> categoryDTOS = this.itemClient.listCategoryByIds(categoryIds);
                        filterResult.put("分类",categoryDTOS );
                    }
                    return filterResult;
                }*/  //以上抽取为如下方法


                this::apply
        );
    }

    private Map<String, List<?>> apply(Aggregations aggregations) {
        Map<String, List<?>> filterResult = new LinkedHashMap<>();//可以排序
        //解析聚合
        //ParsedLongTerms  根据聚合名称 获得聚合结果对象 buckets[]

        //根据聚合名称 获取聚合结果


        Terms categoryAgg = aggregations.get("categoryAgg");
        //从聚合结果中获取当前 聚合结果key 的值
        List<Long> categoryIds = categoryAgg.getBuckets()
                .stream()
                .map(bucket -> ((Terms.Bucket) bucket)
                        .getKeyAsNumber())
                .map(Number::longValue)
                .collect(Collectors.toList());

        if (!CollectionUtils.isEmpty(categoryIds)) {//不为空去查
            List<CategoryDTO> categoryDTOS = this.itemClient.listCategoryByIds(categoryIds);
            filterResult.put("分类", categoryDTOS);
        }

        Terms brandAgg = aggregations.get("brandAgg");
        List<Long> brandIds = brandAgg
                .getBuckets()
                .stream()
                .map(bucket -> ((Terms.Bucket) bucket).getKeyAsNumber())
                .map(Number::longValue)
                .collect(Collectors.toList());

        if (!CollectionUtils.isEmpty(brandIds)) {//不为空去查
            List<BrandDTO> brandDTOS = this.itemClient.listBrandByBid(brandIds);
            filterResult.put("品牌", brandDTOS);
        }

        //根据聚合名称获取nested集合
        Nested specsAgg = aggregations.get("specsAgg");
        //根据聚合名称获取nested中的名称集合
        Terms nameAgg = specsAgg.getAggregations().get("nameAgg");
        nameAgg.getBuckets().forEach(bucket -> {
            //获取 key  就是规格参数的名称
            String resultKey = ((Terms.Bucket) bucket).getKeyAsString();
            //根据聚合名称获取集合结果
            Terms valueAgg = ((Terms.Bucket) bucket).getAggregations().get("valueAgg");
            //从value的聚合结果中获取 结果集合
            List<String> valueList = valueAgg.getBuckets().stream().map(valueBucket -> {
                return ((Terms.Bucket) valueBucket).getKeyAsString();
            }).collect(Collectors.toList());
            //吧对应的key 和value 集合存入filterResult 返回给前端
            filterResult.put(resultKey, valueList);
        });


        return filterResult;
    }


}
